rich-megabyte-73517
04/14/2026, 8:45 PMtall-airline-16756
04/14/2026, 8:45 PMtall-airline-16756
04/14/2026, 8:46 PMparticipant_joined and participant_left and read the disconnected_reason on left events (e.g., USER_REJECTED, CONNECTION_TIMEOUT, CLIENT_INITIATED) to classify rejections and failures.
• Detect “ring forever” / unanswered calls by combining join-timeouts and silence/interaction monitoring (no join or no audio/activity within a threshold).
Practical best practices to implement
1. Enable webhooks and record these events
◦ Subscribe to participant_joined, participant_left, room_started, room_finished, ingress_started, ingress_ended.
◦ Persist event ID, timestamps, participant id, and disconnected_reason so you can audit outcomes and deduplicate events.
1. Classify call outcomes
◦ Rejected: participant_left with USER_REJECTED or similar explicit reason.
◦ Failed/Timed out: CONNECTION_TIMEOUT or no participant_joined within setup timeout.
◦ Unanswered/ringing-forever: no agent/client join and no activity within configured ring timeout.
1. Implement timeouts & silence detection
◦ Set a sensible setup/ring timeout (e.g., N seconds). If no participant_joined by then, mark as unanswered.
◦ Run a short silence/activity detector after join (e.g., track last audio or interaction). If silence exceeds threshold, treat as voicemail/abandoned and close/flag the call.
1. Use fast, reliable webhook handling
◦ Acknowledge webhooks quickly (return 2xx) and process asynchronously to avoid missed or retried events.
◦ Store events durably so you can reconstruct call flows for later analysis.
1. Correlate logs and agent telemetry
◦ Combine webhook events with server logs, SIP lifecycle events, and any agent observability/traces to diagnose why calls were rejected or never connected.
◦ Keep timestamps and unique IDs to correlate across systems.
1. For outbound flows, wait-for-join pattern
◦ For controlled outbound calls, wait for the remote participant to actually join (or hit your join timeout) before marking the call as delivered; otherwise mark as undelivered/rejected.
If you want, I can:
• Suggest specific timeout values and an event schema for storing call outcomes.
• Provide a sample webhook-processing flow or example code for silence detection.
Which would you like next?
Sources: Server-side | livekit/livekit_composite | DeepWiki | How to detect and handle silence during calls | Best practices for managing webhook event streams | LiveKit | Job lifecycle | LiveKit Documentation | Streamline troubleshooting with Agent Observability | LiveKit, with interactive elementsrich-megabyte-73517
04/14/2026, 8:51 PMtall-airline-16756
04/14/2026, 8:52 PMparticipant_joined, participant_left, participant_connection_aborted, room_started, room_finished).
How to enable
1. Dashboard:
◦ Go to Settings → Webhooks in your LiveKit project dashboard and add your endpoint URL.
1. Programmatically:
◦ Use your server SDK / LiveKit API to configure the webhook endpoint at the project level (same endpoint receives SIP-related room/participant events).
What you’ll receive and how to use it
• Events: participant_joined, participant_left (with disconnected_reason), participant_connection_aborted, room_started, room_finished, plus egress events if recording is enabled.
• SIP-specific signaling (INVITE/RING) is not sent; infer call outcomes from room/participant events and timing patterns (e.g., no participant_joined within your ring timeout → unanswered).
• Webhook requests include an Authorization header with a signed JWT and Content-Type: application/webhook+json — verify the signature.
Best practices
• Acknowledge webhooks quickly (return 2xx) and process asynchronously.
• Make handlers idempotent (events may be retried).
• Correlate event IDs, timestamps, and participant IDs with your SIP call identifiers to classify rejected vs unanswered calls.
• Enable auto-egress if you want `egress_started`/`egress_ended` events for recordings.
If you’d like, I can:
• Provide a sample webhook handler (signature verification + durable storage schema).
• Suggest ring/setup timeout values and an event-to-outcome mapping you can store. Which would be most helpful? Sources: Best practices for managing webhook event streams | LiveKit | sip | livekit/livekit_composite | DeepWiki, with interactive elementsrich-megabyte-73517
04/14/2026, 8:53 PMtall-airline-16756
04/14/2026, 8:53 PMrich-megabyte-73517
04/14/2026, 8:56 PMtall-airline-16756
04/14/2026, 8:57 PMparticipant_joined and participant_left. That will cover most cases if you apply a couple of rules.
How to use them simply
• If you see participant_joined → treat the call as connected.
• When participant_left arrives, read disconnected_reason to classify outcome (e.g., USER_REJECTED, CONNECTION_TIMEOUT, CLIENT_INITIATED).
• If no participant_joined occurs within your ring/setup timeout (e.g., 30s), mark the call as unanswered/undelivered.
Minimal implementation checklist
• Subscribe to participant_joined and participant_left webhooks.
• Persist event IDs, timestamps, and participant IDs.
• Implement a single ring timer per outbound call that fires if participant_joined hasn’t been received by timeout.
• On participant_left, store disconnected_reason and correlate with the timer (cancel timer if joined).
Caveats / quick tips
• participant_left + disconnected_reason is the primary source for rejections, but SIP-specific signals like INVITE/RING aren’t provided.
• Make webhook handling idempotent and acknowledge quickly (2xx).
• 30s is a safe default timeout; shorter (10–15s) for high-volume flows, longer for slower destinations.
If you want, I can give a one-file example webhook handler (verify signature, start/clear timer, store outcome). Which language do you prefer?
, with interactive elementsrich-megabyte-73517
04/14/2026, 8:57 PMtall-airline-16756
04/14/2026, 8:58 PMringing, in-progress (answered), no-answer, busy, failed.
◦ Enable LiveKit webhooks to receive participant_joined, participant_left, and participant_connection_aborted.
1. Correlate events:
◦ Use a shared call identifier (e.g., Twilio CallSid stored in room metadata or participant identity) to join Twilio callbacks with LiveKit events.
◦ Use Twilio ringing → if LiveKit participant_joined follows, call answered; if Twilio reports no-answer or you never see participant_joined by your timeout, mark unanswered.
◦ Use Twilio busy or failed plus LiveKit participant_left / participant_connection_aborted to classify rejections or failures.
Why this is useful
• Twilio gives SIP/PSTN-level signaling you don’t get from LiveKit alone (INVITE/RING/BUSY), while LiveKit gives room/participant lifecycle and media state. Combining both gives reliable outcomes with minimal custom timers.
If you want, I can:
• Provide a small example showing how to attach Twilio CallSid to LiveKit room/participant metadata and a webhook correlation flow.
• Or give a sample Twilio callback handler and LiveKit webhook handler that maps outcomes to a single call record.
Which example would you like?
, with interactive elementsrich-megabyte-73517
04/14/2026, 9:04 PMtall-airline-16756
04/14/2026, 9:05 PMparticipant_left and its disconnected_reason (e.g., USER_REJECTED, CONNECTION_TIMEOUT) to classify rejects.
• Agent Insights (if enabled): replay synchronized audio, transcripts, and traces to diagnose long ringing or rejections.
How to identify rejected vs ringing-forever
• Rejected: find participant_left with disconnected_reason = USER_REJECTED or a short room lifecycle (room_started → room_finished quickly).
• Ringing-forever / unanswered: look for rooms with room_started but no participant_joined within your ring timeout, or very long time-to-join in the session list.
Tips for using the dashboard effectively
• Apply filters (time range, trunk, dispatch rule, participant id) to narrow results.
• Sort or filter by call duration — very short lifetimes often indicate rejections; very long pre-join durations indicate ringing.
• Correlate dashboard entries with Twilio CallSid (if using Twilio) by storing CallSid in room/participant metadata so you can join Twilio callbacks with dashboard records.
If you want, I can:
• Provide exact steps/screens to filter the Call Logs for disconnected_reason and long pre-join durations, or
• Give a short example of metadata naming (e.g., store twilio_callsid in participant identity) so dashboard rows correlate with Twilio events.
Which would you like?
Sources: Say hello to improved Cloud analytics | LiveKit | Best practices for managing webhook event streams | LiveKit | Agents (Python) | livekit/livekit_composite | DeepWiki | Streamline troubleshooting with Agent Observability | LiveKit | Why is my Agent insights data missing? | LiveKit, with interactive elementsrich-megabyte-73517
04/14/2026, 9:11 PMtall-airline-16756
04/14/2026, 9:12 PMdisconnected_reason = USER_REJECTED + duration < 5s + trunk = X)
I’ll then give step‑by‑step filter instructions or a saved-filter example you can paste into the dashboard.
, with interactive elementsrich-megabyte-73517
04/14/2026, 9:13 PMrich-megabyte-73517
04/14/2026, 9:13 PMtall-airline-16756
04/14/2026, 9:13 PM< 5s, > 30s).
◦ For disconnected_reason, select one or multiple values (e.g., USER_REJECTED, CONNECTION_TIMEOUT).
1. Combine filters: each added filter narrows results (AND logic). Use multiple values inside a single filter for OR behavior when available.
2. Sort and inspect results: sort by start time or duration to surface short/failed calls or long pre-join sessions.
3. Save or export:
◦ Save the current filter as a preset (if the dashboard offers Save Filter).
◦ Export the filtered rows to CSV if you need offline analysis.
Concrete example filter to find short rejections on a trunk
• Time range: Last 7 days
• Trunk = MySIPTrunk
• disconnected_reason IN (USER_REJECTED, CONNECTION_TIMEOUT)
• Duration < 5s
If your dashboard only shows a single “status” filter, tell me whether you see a filter icon or a column header menu in Call Logs — I can give exact clicks or a screenshot walkthrough for your UI. Which would you prefer?
, with interactive elementstall-airline-16756
04/14/2026, 9:13 PMtrunk, dispatch_rule, disconnected_reason, duration) and choose the filter operator (equals, contains, , ).
1. Combine multiple column filters — the dashboard applies them with AND logic so results match all criteria.
2. To filter by short duration: open the duration column filter and set < 5 (seconds).
3. To show rejected calls: open disconnected_reason and select USER_REJECTED (or type it into the filter).
4. To scope to a trunk or dispatch rule: filter trunk = your_trunk_name and/or dispatch_rule = your_rule.
Save and reuse
• After composing filters, click Save (or Save view/preset) to persist the filter set for quick reuse.
• Use Export (CSV) on the filtered result set if you want offline analysis.
Example filter (rejected, short calls on trunk X)
• Time range: Last 7 days
• trunk = trunk-X
• disconnected_reason = USER_REJECTED
• duration < 5
• (Optional) global search: participant_id:agent-123 or callSid:CAxxxx
If you don’t see specific columns (e.g., disconnected_reason), enable them from the column selector (gear or Columns menu) in the Call Logs view.
Would you like a screenshot-style walkthrough for each click, or the exact field names to use for creating a saved preset? Which do you prefer?
, with interactive elements